package com.starview.star.compiler.generate;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;

import com.starview.platform.data.type.IType;
import com.starview.platform.data.type.ITypeVisitor;
import com.starview.platform.data.type.Location;
import com.starview.platform.data.type.QuantifiedType;
import com.starview.platform.data.type.Type;
import com.starview.platform.data.type.TypeExp;
import com.starview.platform.data.type.TypeInterfaceType;
import com.starview.platform.data.type.TypeUtils;
import com.starview.platform.data.type.TypeVar;
import com.starview.platform.data.type.UniversalType;
import com.starview.star.compiler.ErrorReport;
import com.starview.star.compiler.ast.Abstract;
import com.starview.star.compiler.ast.IAbstract;
import com.starview.star.compiler.cafe.CafeSyntax;
import com.starview.star.compiler.cafe.Names;
import com.starview.star.compiler.transform.OverLoader;
import com.starview.star.compiler.util.AccessMode;

public class TypeAbstract implements ITypeVisitor
{
  private Stack<IAbstract> stack = new Stack<IAbstract>();
  private final Location loc;

  protected TypeAbstract(Location loc)
  {
    this.loc = loc;
  }

  public static IAbstract typeToAbstract(Location loc, IType type, ErrorReport errors)
  {
    TypeAbstract generator = new TypeAbstract(loc);
    return generator.convertType(type);
  }

  public IAbstract convertType(IType type)
  {
    type.accept(this);
    assert stack.size() == 1;
    return stack.pop();
  }

  @Override
  public void visitSimpleType(Type t)
  {
    stack.push(Abstract.name(loc, t.typeLabel()));
  }

  @Override
  public void visitTypeExp(TypeExp t)
  {
    if (TypeUtils.isOverloadedType(t)) {
      TypeUtils.getOverloadedContract(t).accept(this);
      IAbstract arg = stack.pop();
      TypeUtils.getOverloadedType(t).accept(this);
      IAbstract res = stack.pop();
      stack.push(CafeSyntax.apply(loc, Names.ARROW, arg, res));
    } else {
      t.getTypeCon().accept(this);
      IAbstract tyCon = stack.pop();

      int arity = t.typeArity();
      for (int ix = arity - 1; ix >= 0; ix--)
        t.getTypeArg(ix).accept(this);
      List<IAbstract> typeArgs = new ArrayList<IAbstract>();
      for (int ix = 0; ix < arity; ix++)
        typeArgs.add(stack.pop());

      stack.push(CafeSyntax.apply(loc, tyCon, typeArgs));
    }
  }

  @Override
  public void visitTypeVar(TypeVar v)
  {
    String name = v.getVarName();
    Location loc = v.getBindingLocation();
    if (loc == null)
      loc = this.loc;

    switch (v.kind().mode()) {
    case type:
    case unknown:
      stack.push(CafeSyntax.typeVar(loc, name));
      break;
    case typefunction:
      stack.push(CafeSyntax.typeFunVar(loc, name, v.typeArity()));
      break;
    }
  }

  @Override
  public void visitUniversalType(UniversalType t)
  {
    if (TypeUtils.hasContractDependencies(t))
      t = (UniversalType) OverLoader.computeDictionaryType(t, loc, AccessMode.readOnly);

    t.getBoundVar().accept(this);
    IAbstract tV = stack.pop();

    t.getBoundType().accept(this);
    IAbstract bound = stack.pop();
    stack.push(CafeSyntax.universalType(loc, tV, bound));
  }

  @Override
  public void visitExistentialType(QuantifiedType t)
  {
    t.getBoundVar().accept(this);
    IAbstract tV = stack.pop();

    t.getBoundType().accept(this);
    IAbstract bound = stack.pop();
    stack.push(CafeSyntax.existentialType(loc, tV, bound));
  }

  @Override
  public void visitTypeInterface(TypeInterfaceType t)
  {
    String label = t.typeLabel();

    Map<String, IType> members = t.getAllFields();
    IType[] argTypes = new IType[members.size()];
    int ix = 0;
    for (Entry<String, IType> entry : members.entrySet()) {
      argTypes[ix++] = entry.getValue();
    }
    TypeUtils.typeExp(label, argTypes).accept(this);
  }
}